/* This program is free software. It comes without any warranty, to
 * the extent permitted by applicable law. You can redistribute it
 * and/or modify it under the terms of the Do What The Fuck You Want
 * To Public License, Version 2, as published by Sam Hocevar. See
 * http://sam.zoy.org/wtfpl/COPYING for more details. */ 
package de.rockon.fuzzy.io;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;

import de.rockon.fuzzy.controller.model.FuzzyController;
import de.rockon.fuzzy.controller.model.FuzzyPoint;
import de.rockon.fuzzy.controller.model.FuzzySet;
import de.rockon.fuzzy.controller.model.FuzzyVariable;
import de.rockon.fuzzy.controller.model.enums.DefuzzyfyType;
import de.rockon.fuzzy.controller.model.enums.VariableType;
import de.rockon.fuzzy.controller.model.rules.Rule;
import de.rockon.fuzzy.controller.util.Logger;

/**
 * Klasse zum xml import und export
 */
public class ImportExportUtil {

	/**
	 * �ffnet ein Popupfenster um ein Datei zum Laden auszuw�hlen
	 */
	public static String chooseFileToLoad(JFrame fuzzyFrame, FileNameExtensionFilter filter) {
		JFileChooser chooser = new JFileChooser(new File("data"));		
		chooser.setFileFilter(filter);
		int returnVal = chooser.showOpenDialog(fuzzyFrame);
		if (returnVal == JFileChooser.CANCEL_OPTION) {
			return null;
		}
		return chooser.getSelectedFile().getAbsolutePath();
	}
	
	/**
	 * �ffnet ein Popupfenster um ein Datei zum Speichern auszuw�hlen
	 */
	public static String chooseFileToSave(JFrame fuzzyFrame, FileNameExtensionFilter filter) {
		JFileChooser chooser = new JFileChooser(new File("data"));		
		chooser.setFileFilter(filter);
		int returnVal = chooser.showSaveDialog(fuzzyFrame);
		if (returnVal == JFileChooser.CANCEL_OPTION) {
			return null;
		}

		String fileName = chooser.getSelectedFile().getAbsolutePath();
		final int index = fileName.lastIndexOf(".");
		if (index > 0) {
			fileName = fileName.substring(0, index);
		}
		final String extension = filter.getExtensions()[0];
		final String newFileName = fileName + "." + extension;

		return newFileName;
	}

	/**
	 * exportiert unsere Datenstruktur in eine XML-Datei
	 * 
	 * @param fileName
	 */
	public static void xmlExport(String fileName) {
		/* fuzzyvariablen export */
		FuzzyController container = FuzzyController.getInstance();
		try {
			/* Create document */
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder loader = factory.newDocumentBuilder();

			Document document = loader.newDocument();
			document.appendChild(document.createElement("fuzzycontroller"));

			NodeList fuzzySetsContainerNodelist = document.getChildNodes();
			Node fuzzySetsContainerNode = fuzzySetsContainerNodelist.item(0);
			/* temp Element */
			Element neuE = null;

			for (int i = 0; i < container.size(); i++) {

				FuzzyVariable sets = container.getChild(i);

				if (sets.isTemp()) {
					continue;
				}
				/* add new 'fuzzySets' element with attributes */
				neuE = document.createElement("fuzzyvariable");
				neuE.setAttributeNode(document.createAttribute("name"));
				neuE.setAttributeNode(document.createAttribute("type"));
				if (sets.getType().toString() == "output") {
					neuE.setAttributeNode(document.createAttribute("defuzzyfier"));
				}
				neuE.setAttributeNode(document.createAttribute("domainLower"));
				neuE.setAttributeNode(document.createAttribute("domainUpper"));
				neuE.setAttributeNode(document.createAttribute("unit"));
				fuzzySetsContainerNode.appendChild(neuE);

				NodeList fuzzySetsNodelist = fuzzySetsContainerNode.getChildNodes();
				Node fuzzySetsNode = fuzzySetsNodelist.item(fuzzySetsNodelist.getLength() - 1);

				fuzzySetsNode.getAttributes().getNamedItem("name").setNodeValue(sets.getName());
				fuzzySetsNode.getAttributes().getNamedItem("type").setNodeValue(sets.getType().toString());
				if (sets.getType().toString() == "output") {
					fuzzySetsNode.getAttributes().getNamedItem("defuzzyfier").setNodeValue(sets.getDefuzzyfyType().toString());
				}
				fuzzySetsNode.getAttributes().getNamedItem("domainLower").setNodeValue(String.valueOf(sets.getDomain()[0]));
				fuzzySetsNode.getAttributes().getNamedItem("domainUpper").setNodeValue(String.valueOf(sets.getDomain()[1]));
				fuzzySetsNode.getAttributes().getNamedItem("unit").setNodeValue(sets.getUnit());

				Logger.debug(sets.getName() + " type:" + sets.getType() + " unit:" + sets.getUnit() + " domainLower:"
						+ String.valueOf(sets.getDomain()[0]) + " domainUpper:" + String.valueOf(sets.getDomain()[1]));

				for (int j = 0; j < sets.size(); j++) {
					FuzzySet set = sets.getChild(j);

					/* add new 'fuzzySets' ekenebt with 'name' attribute */
					neuE = document.createElement("fuzzyset");
					neuE.setAttributeNode(document.createAttribute("name"));
					fuzzySetsNode.appendChild(neuE);
					NodeList fuzzySetNodelist = fuzzySetsNode.getChildNodes();
					Node fuzzySetNode = fuzzySetNodelist.item(j);
					fuzzySetNode.getAttributes().getNamedItem("name").setNodeValue(set.getName());

					Logger.debug("\t" + set.getName());

					for (int k = 0; k < set.size(); k++) {
						FuzzyPoint p = set.getChild(k);

						/* add new 'point' element with 'x' and 'y' attribute */
						neuE = document.createElement("point");
						neuE.setAttributeNode(document.createAttribute("x"));
						neuE.setAttributeNode(document.createAttribute("y"));
						fuzzySetNode.appendChild(neuE);
						NodeList pointNodelist = fuzzySetNode.getChildNodes();
						Node pointNode = pointNodelist.item(k);
						pointNode.getAttributes().getNamedItem("x").setNodeValue(String.valueOf(p.getX()));
						pointNode.getAttributes().getNamedItem("y").setNodeValue(String.valueOf(p.getY()));
						Logger.debug("\t\tPunkt: (" + p.getX() + "," + p.getY() + ")");
					}
				}
			}

			/* fuzzyrules export */
			neuE = document.createElement("fuzzyrules");
			fuzzySetsContainerNode.appendChild(neuE);
			NodeList fuzzyRulesNodelist = document.getElementsByTagName("fuzzyrules");

			Node fuzzyRulesNode = fuzzyRulesNodelist.item(0);

			/* rule export */
			Logger.debug("FuzzyRuleBaseSize: " + container.getRuleBase().size());
			for (int j = 0; j < container.getRuleBase().size(); j++) {
				Rule fuzzyRule = container.getRuleBase().get(j);

				neuE = document.createElement("rule");
				neuE.setAttributeNode(document.createAttribute("operator"));
				fuzzyRulesNode.appendChild(neuE);

				NodeList ruleNodelist = fuzzyRulesNode.getChildNodes();
				Node ruleNode = ruleNodelist.item(j);

				ruleNode.getAttributes().getNamedItem("operator").setNodeValue(fuzzyRule.getOperatorTypeAsString());

				neuE = document.createElement("input");
				ruleNode.appendChild(neuE);
				neuE = document.createElement("output");
				ruleNode.appendChild(neuE);

				/* input export */
				Iterator<FuzzySet> iter = fuzzyRule.getInput().iterator();
				NodeList inputNodelist = ruleNode.getChildNodes();
				Node inputNode = inputNodelist.item(0);

				Logger.debug("add Rule (operator: " + fuzzyRule.getOperatorTypeAsString() + " )");
				Logger.debug("\tInput: ");
				int i = 0;
				while (iter.hasNext()) {
					FuzzySet set = iter.next();

					// wenn kein set gesetzt wurde bzw das set kein parent hat, einfach weiter
					if (set == null || set.getParent() == null) {
						continue;
					}
					Logger.debug("\t\t(input) SetName: " + set.getName() + " VariablenName: " + set.getParent().getName());

					String fuzzyInputSetName = set.getName();
					String fuzzyInputVariableName = set.getParent().getName();
					neuE = document.createElement("item");
					neuE.setAttributeNode(document.createAttribute("variable"));
					neuE.setAttributeNode(document.createAttribute("set"));

					inputNode.appendChild(neuE);
					NodeList itemNodelist = inputNode.getChildNodes();
					Node itemNode = itemNodelist.item(i);

					itemNode.getAttributes().getNamedItem("variable").setNodeValue(fuzzyInputVariableName);
					itemNode.getAttributes().getNamedItem("set").setNodeValue(fuzzyInputSetName);
					i++;
				}

				/* ouput export */
				NodeList outputNodelist = ruleNode.getChildNodes();
				Node outputNode = outputNodelist.item(1);

				// wurde kein output im regeleditor gesetzt, einfach weiter!
				if (fuzzyRule.getOutput() == null) {
					Logger.info("fuzzyRule.getOutput() == null skippe");
					continue;
				}
				FuzzySet fuzzyOutputSet = fuzzyRule.getOutput();
				String fuzzyOuputItemVariableName = fuzzyOutputSet.getParent().getName();
				String fuzzyOutputItemSetName = fuzzyOutputSet.getName();
				neuE = document.createElement("item");
				neuE.setAttributeNode(document.createAttribute("variable"));
				neuE.setAttributeNode(document.createAttribute("set"));

				outputNode.appendChild(neuE);
				NodeList outputItemNodelist = outputNode.getChildNodes();
				Node outputItemNode = outputItemNodelist.item(0);

				outputItemNode.getAttributes().getNamedItem("variable").setNodeValue(fuzzyOuputItemVariableName);
				outputItemNode.getAttributes().getNamedItem("set").setNodeValue(fuzzyOutputItemSetName);
				Logger.debug("add Output:");
				Logger.debug("\t\t(output) SetName: " + fuzzyOutputSet + " VariablenName: " + fuzzyOuputItemVariableName);
			}

			/* write to file */
			OutputFormat format = new OutputFormat(document);
			format.setIndenting(true);
			format.setIndent(4);
			XMLSerializer serializer = new XMLSerializer(format);
			serializer.setOutputCharStream(new java.io.FileWriter(fileName));
			serializer.serialize(document);

			File file = new File(fileName);
			container.setName(file.getName());
			Logger.info("Data successfully saved into file: " + fileName);
		} catch (Exception ex) {
			ex.printStackTrace();
			container.clear();
			container = null;
		}
	}

	/**
	 * @param fuzzyFrame
	 */
	public static void xmlExportDialog(JFrame fuzzyFrame) {
		String filename = ImportExportUtil.chooseFileToSave(fuzzyFrame, new FileNameExtensionFilter("fXML - Fuzzy XML-Files", "fxml"));
		if (filename != null) {
			ImportExportUtil.xmlExport(filename);
		}
	}

	/**
	 * importiert eine XML-Datei und f�ght sie in unsere Datenstruktur hinzu
	 * 
	 * @param fileName
	 */
	public static boolean xmlImport(String fileName) {
		FuzzyController container = FuzzyController.getInstance();
		try {
			File file = new File(fileName);
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

			DocumentBuilder db = dbf.newDocumentBuilder();
			Document doc = db.parse(file);
			doc.getDocumentElement().normalize();
			container.clear();

			Element element = doc.getDocumentElement();
			if (!element.getNodeName().equals("fuzzycontroller")) {
				Logger.error("LoadingError - Not a Fuzzy XML-File: " + fileName);
				throw new IOException("LoadingError - Not a Fuzzy XML-File" + fileName);
			}

			/* fuzzyvariablen import */
			NodeList fuzzySetsContainerNodelist = doc.getElementsByTagName("fuzzyvariable");
			for (int j = 0; j < fuzzySetsContainerNodelist.getLength(); j++) {
				Node fuzzySetsNode = fuzzySetsContainerNodelist.item(j);
				NodeList fuzzySetsNodelist = fuzzySetsNode.getChildNodes();

				String fuzzyVariableName = fuzzySetsNode.getAttributes().getNamedItem("name").getNodeValue();
				String fuzzyVariableUnit = fuzzySetsNode.getAttributes().getNamedItem("unit").getNodeValue();
				double[] fuzzyVariableRange = {
						Double.parseDouble(fuzzySetsNode.getAttributes().getNamedItem("domainLower").getNodeValue()),
						Double.parseDouble(fuzzySetsNode.getAttributes().getNamedItem("domainUpper").getNodeValue()) };

				VariableType fuzzyVariableType = VariableType.INPUT;
				if (fuzzySetsNode.getAttributes().getNamedItem("type").getNodeValue().equals("input")) {
					fuzzyVariableType = VariableType.INPUT;
				}
				if (fuzzySetsNode.getAttributes().getNamedItem("type").getNodeValue().equals("output")) {
					fuzzyVariableType = VariableType.OUTPUT;
				}
				FuzzyVariable fuzzySets = new FuzzyVariable(fuzzyVariableName, fuzzyVariableType, fuzzyVariableUnit, fuzzyVariableRange);

				if (fuzzySets.getType().equals(VariableType.OUTPUT)) {
					if (DefuzzyfyType.FIRST_MAXIMUM.toString().equals(
							fuzzySetsNode.getAttributes().getNamedItem("defuzzyfier").getNodeValue())) {
						fuzzySets.setDefuzzyfyType(DefuzzyfyType.FIRST_MAXIMUM);
					}
					if (DefuzzyfyType.BALANCE_POINT.toString().equals(
							fuzzySetsNode.getAttributes().getNamedItem("defuzzyfier").getNodeValue())) {
						fuzzySets.setDefuzzyfyType(DefuzzyfyType.BALANCE_POINT);
					}
					if (DefuzzyfyType.LAST_MAXIMUM.toString().equals(
							fuzzySetsNode.getAttributes().getNamedItem("defuzzyfier").getNodeValue())) {
						fuzzySets.setDefuzzyfyType(DefuzzyfyType.LAST_MAXIMUM);
					}
					if (DefuzzyfyType.AVG_MAXIMUM.toString().equals(
							fuzzySetsNode.getAttributes().getNamedItem("defuzzyfier").getNodeValue())) {
						fuzzySets.setDefuzzyfyType(DefuzzyfyType.AVG_MAXIMUM);
					}
				}

				fuzzySets.setDomain(fuzzyVariableRange);
				fuzzySets.setUnit(fuzzyVariableUnit);
				Logger.debug("found: " + fuzzyVariableName + " type:" + fuzzyVariableType + " unit:" + fuzzyVariableUnit + " domainLower:"
						+ fuzzyVariableRange[0] + " domainUpper:" + fuzzyVariableRange[1] + " " + fuzzyVariableUnit);

				for (int s = 0; s < fuzzySetsNodelist.getLength(); s++) {
					Node fuzzySetNode = fuzzySetsNodelist.item(s);
					NodeList points = fuzzySetNode.getChildNodes();

					FuzzySet fuzzySet = new FuzzySet(null);
					String fuzzySetName = "Error";
					if (fuzzySetNode.getNodeType() == 1) {
						fuzzySetName = fuzzySetNode.getAttributes().getNamedItem("name").getNodeValue();
						Logger.debug("\tfound: FuzzySet: " + fuzzySetName);
					}

					if (fuzzySetNode.hasChildNodes() == true) {
						fuzzySet.setName(fuzzySetName);
						fuzzySets.add(fuzzySet);
						Logger.debug("\tadded: Fuzzyset \"" + fuzzySet.getName() + "\" to FuzzySets");
					}

					for (int i = 0; i < points.getLength(); i++) {
						Node pointNode = points.item(i);
						if (pointNode.getNodeType() == Node.ELEMENT_NODE) {
							Element pointElement = (Element) pointNode;
							double x = Double.parseDouble(pointElement.getAttribute("x"));
							double y = Double.parseDouble(pointElement.getAttribute("y"));
							FuzzyPoint point = new FuzzyPoint(x, y);
							fuzzySet.add(point);
							Logger.debug("\t\tnew: Point(" + x + "," + y + ")");
						}
					}
				}
				container.add(fuzzySets);
				Logger.debug("added: Fuzzysets \"" + fuzzySets.getName() + "\" to FuzzySetsContainers");
			}

			/* fuzzyrules import */
			NodeList fuzzyRulesContainerNodelist = doc.getElementsByTagName("fuzzyrules");
			for (int i = 0; i < fuzzyRulesContainerNodelist.getLength(); i++) {
				Node fuzzyRulesNode = fuzzyRulesContainerNodelist.item(i);
				NodeList fuzzyRulesNodelist = fuzzyRulesNode.getChildNodes();

				for (int k = 0; k < fuzzyRulesNodelist.getLength(); k++) {
					Node fuzzyRuleNode = fuzzyRulesNodelist.item(k);
					NodeList fuzzyRuleNodelist = fuzzyRuleNode.getChildNodes();
					Rule fuzzyRule = new Rule(Rule.AND_MIN_OPERATOR);

					for (int l = 0; l < fuzzyRuleNodelist.getLength(); l++) {
						// einlesen der inputrules
						Node ioNode = fuzzyRuleNodelist.item(l);
						if (ioNode.hasChildNodes()) {
							Logger.debug("\tfound: fuzzyRule");

							NodeList ioNodelist = ioNode.getChildNodes();
							for (int j = 0; j < ioNodelist.getLength(); j++) {
								Node itemNode = ioNodelist.item(j);
								if (itemNode.hasAttributes()) {
									String ioRuleFuzzyVariable = itemNode.getAttributes().getNamedItem("variable").getNodeValue();
									String ioRuleSet = itemNode.getAttributes().getNamedItem("set").getNodeValue();
									FuzzySet ioFuzzySet = container.getChildByName(ioRuleFuzzyVariable).getChildByName(ioRuleSet);
									if (ioNode.getNodeName() == "input") {
										fuzzyRule.addInput(ioFuzzySet);
										Logger.debug("\t\tadded:->rule->input-->fuzzyVariable: " + ioRuleFuzzyVariable + " fuzzySet: "
												+ ioRuleSet);
									} else if (ioNode.getNodeName() == "output") {
										fuzzyRule.setOutput(ioFuzzySet);
										Logger.debug("\t\tadded:->rule->output->fuzzyVariable: " + ioRuleFuzzyVariable + " fuzzySet: "
												+ ioRuleSet);
									}
								}
							}
						}
					}
					if (fuzzyRuleNode.hasChildNodes()) {
						container.getRuleBase().add(fuzzyRule);
						fuzzyRule = null;
						Logger.debug("\tadded:-->fuzzyRule zu fuzzyController hinzugef�ght");
					}
				}
			}

			container.setName(file.getName());
			Logger.info("Data successfully loaded from file: " + fileName);
		} catch (Exception e) {
			Logger.info("Data couldn't be found/loaded: " + fileName);
			System.out.println("Data couldn't be found/loaded: " + fileName);
			container.clear();
			return false;
		}
		return true;
	}

	/**
	 * @param fuzzyFrame
	 */
	public static boolean xmlImportDialog(JFrame fuzzyFrame) {
		String filename = ImportExportUtil.chooseFileToLoad(fuzzyFrame, new FileNameExtensionFilter("fXML - Fuzzy XML-Files", "fxml", "xml"));
		if (filename != null) {
			return ImportExportUtil.xmlImport(filename);
		}
		return false;
	}
}